﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Media;
using Microsoft.Maps.MapControl;
using System.Linq;
using MyFloridaHouse.libSvOpenDistricts.DataService;
using MyFloridaHouse.ServiceConn.DistrBldrSvcRef;
using MyFloridaHouse.libSvUtilities.CustomColors;
using MyFloridaHouse.libSvBingMapsMultiShape;
using System.Windows.Controls;
using System.Windows;

namespace MyFloridaHouse.libSvOpenDistricts.MapShapes
{
    public partial class MapShapeLayer : INotifyPropertyChanged
    {
        #region property changed
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        #endregion

        private Map myBingMap;
        private MapLayer myBingMapLayer;
        private ServiceDAL serviceDAL;

        public List<Region> layerRegionList;

        private int _layerID;
        public int layerID { get { return _layerID; } }

        private string _layerName;
        public string layerName { get { return _layerName; } }

        private string _layerAbbr;
        public string layerAbbr { get { return _layerAbbr; } }

        private string _shapeType;
        public string shapeType { get { return _shapeType; } }

        private SolidColorBrush _lineStroke;
        public SolidColorBrush lineStroke { get { return _lineStroke; } }

        private bool _isFillByRegionIndexType;
        public bool isFillByRegionIndexType {
            get { return _isFillByRegionIndexType; }
            set { _isFillByRegionIndexType = value; }
         }


        private int _SelectedShapeIndex;
        public int SelectedShapeIndex
        {
            get { return _SelectedShapeIndex; }
            set { _SelectedShapeIndex = value; NotifyPropertyChanged("SelectedShapeIndex"); }
        }
        private string _layerColor;
        public string layerColor
        {
            get { return _layerColor; }
            set
            {
                _layerColor = value;
                _lineStroke = NamedColors.ConvertStringToColor(value);

                NotifyPropertyChanged("layerColor");
                NotifyPropertyChanged("lineStroke");
                NotifyPropertyChanged("SelectedColorIndex");

            }
        }


        private int _SelectedColorIndex;
        public int SelectedColorIndex
        {
            get { return _SelectedColorIndex; }
            set { _SelectedColorIndex = value; NotifyPropertyChanged("SelectedColorIndex"); }
        }

        private double _minZoomLevel;
        public double minZoomLevel
        {
            get { return _minZoomLevel; }
            set 
            { 
                _minZoomLevel = value;
                NotifyPropertyChanged("minZoomLevel");
                NotifyPropertyChanged("inZoomRange");
                ShowLayer();
            }
        }

        private double _maxZoomLevel;
        public double maxZoomLevel
        {
            get { return _maxZoomLevel; }
            set 
            { 
                _maxZoomLevel = value;
                NotifyPropertyChanged("maxZoomLevel");
                NotifyPropertyChanged("inZoomRange");
                ShowLayer();
            }
        }

        private bool _predefinedRegions;
        public bool predefinedRegions
        {
            get { return _predefinedRegions; }
            set { _predefinedRegions = value; NotifyPropertyChanged("predefinedRegions"); }
        }

        private bool _isConfigLayer;
        public bool isConfigLayer
        {
            get { return _isConfigLayer; }
            set { _isConfigLayer = value; NotifyPropertyChanged("isConfigLayer"); }
        }

        private bool _isBuildLayer;
        public bool isBuildLayer
        {
            get { return _isBuildLayer; }
            set { _isBuildLayer = value; NotifyPropertyChanged("isBuildLayer"); }
        }


        private int _displayOrder;
        public int displayOrder
        {
            get { return _displayOrder; }
            set { _displayOrder = value; NotifyPropertyChanged("displayOrder"); }
        }

        private int _shapeCount;
        public int shapeCount
        {
            get { return _shapeCount; }
            set { _shapeCount = value; NotifyPropertyChanged("shapeCount"); }
        }

        private RegionDetails _regionDetails;
        public RegionDetails regionDetails
        {
            get { return _regionDetails; }
            set { _regionDetails = value; NotifyPropertyChanged("regionDetails"); }
        }

        private ObservableCollection<LayerDataMinMaxItem> _layerDataMinMaxList;
        public ObservableCollection<LayerDataMinMaxItem> layerDataMinMaxList
        {
            get { return _layerDataMinMaxList; }
            set { _layerDataMinMaxList = value; NotifyPropertyChanged("layerDataMinMaxList"); }
        }

        private bool _showLayer;
        public bool showLayer
        {
            get { return _showLayer; }
            set
            {
                _showLayer = value;
                NotifyPropertyChanged("showLayer");
                ShowLayer();

            }
        }

        
        // derived properties
        public bool inZoomRange 
        {
            get { return ((minZoomLevel <= myBingMap.ZoomLevel) & (myBingMap.ZoomLevel <= maxZoomLevel)); }
        }

        public bool isVisible
        {
            get
            {
                return (showLayer & inZoomRange);
            }
        }
        
        public string status { get; set; }
        private ObservableCollection<RegionDTO> LayerRegionIDsInView = new ObservableCollection<RegionDTO>();
        public enum enuMLayerAddLocation
        {
            top,
            bottom
        }
            
        public MapShapeLayer(ServiceDAL iServiceDAL, Map iMyMap, LayerDTO layerData, enuMLayerAddLocation addLocation)
        {
            serviceDAL = iServiceDAL;
            myBingMap = iMyMap;
            SubscribeToEvents();

            myBingMapLayer = new MapLayer();
            if (addLocation == enuMLayerAddLocation.bottom)
            {
                // insert the data layer so it is on bottom of the layer list
                myBingMap.Children.Insert(0, myBingMapLayer);
            }
            else
            {
                // by default last one added is on top
                myBingMap.Children.Add(myBingMapLayer);
            }

            layerRegionList = new List<Region>();

            // non changeable constructor settings
            _layerID = layerData.layerid;
            _shapeType = layerData.layername;
            _layerName = layerData.layername;
            _layerAbbr = layerData.layerabbr;
            _isConfigLayer = layerData.isConfigLayer;
            _isBuildLayer = layerData.isBuildLayer; //jdm: 5/10
            //_districtBuildIndex = layerData.districtBuildIndex;
            _displayOrder = layerData.displayOrder;

            //changeable settings
            isFillByRegionIndexType = false;
            layerColor = layerData.defaultstrokecolor;
            minZoomLevel = layerData.zoommin;
            maxZoomLevel = layerData.zoommax;

            // set defaults
            _SelectedShapeIndex = -1;

            showLayer = false;


            regionDetails = new RegionDetails();
            regionDetails.unselected.stroke = NamedColors.ConvertStringToColor(layerData.defaultstrokecolor);
            regionDetails.unselected.strokeThickness = 2;

            regionDetails.selected.stroke = new SolidColorBrush(Colors.Magenta);
            regionDetails.selected.strokeThickness = 6.0;
        }

        void myBingMap_ViewChangeStart(object sender, MapEventArgs e)
        {
            HideRegions();
        }

        void myBingMap_ViewChangeEnd(object sender, MapEventArgs e)
        {
            if (!showLayer)
            {
                if (predefinedRegions)
                {
                    ClearRegions();
                }
            }
            NotifyPropertyChanged("inZoomRange");
            RefreshShapesAndDataInView();
        }


        public void RefreshShapesAndDataInView()
        {
            // clear all shapes for this layer from the map prior to load (except for District)
            if (layerName!="District")
            {
                myBingMapLayer.Children.Clear();
            }

            if (isVisible)
            {
                if (predefinedRegions)
                {
                        serviceDAL.GetLayerRegionsShapesInView(myBingMap.ZoomLevel, layerID, myBingMap.BoundingRectangle);
                }
                else
                {
                    ShowRegions();

                }
            }
        }

        public void RefreshDataInView()
        {
            if (isVisible)
            {
                if (predefinedRegions)
                {
                    serviceDAL.GetLayerRegionsDataInView(layerID, LayerRegionIDsInView);
                }
            }
        }



        public void LoadRegions(int layerID, ObservableCollection<RegionDTO> regionList, List<RegionDetails> regionDetailList)
        {
            //protect against different sized lists
            if (regionDetailList != null)
            {
                if (regionList.Count != regionDetailList.Count)
                {
                    //todo: Handle this exception in a gracefull manner
                    throw new Exception("regionList and regionDetailList don't match in LoadRegions");
                }
            }


            // clear all shapes for this layer from the map prior to load
            myBingMapLayer.Children.Clear();

            // Now clear everything we know about every region on the layer
            foreach (var rgn in layerRegionList)
            {
                // need to release the region resources before getting rid of the region
                rgn.Dispose();
            }
            layerRegionList.Clear();

            // now load all the new shapes
            for (int rgnIndex = 0; rgnIndex < regionList.Count; rgnIndex++)
            {

                RegionDTO rgnShape = regionList[rgnIndex];

                // create a new region
                Region newRegion = new Region(rgnShape.regionID, myBingMapLayer, new Location(rgnShape.centerLatY, rgnShape.centerLngX), rgnShape.boundingBox);
                newRegion.layer = this;
                newRegion.dataList = new List<RegionDataDTO>();

                // are there custom details for each region?
                if (regionDetailList == null)
                {
                    // no specific region details, use the layer default
                    newRegion.details = regionDetails;
                }
                else
                {
                    // there are custom details provided, use them
                    newRegion.details = regionDetailList[rgnIndex];
                }



                MultiMapShapeBase mapShape = MapShapeTools.BuildShape(rgnShape.mapObject);

                // add the new region  to the layer
                AddMapShapeToRegion(newRegion, mapShape, rgnShape.boundingBox);
                layerRegionList.Add(newRegion);
                ShowRegionIfInView(newRegion);

            }
        }


        public void ReplaceRegionShape(int iRegionID, MultiMapShapeBase mapShape, BoundingBoxDTO boundingBox)
        {
            if (mapShape != null)
            {
                if (mapShape.Vertices != null)
                {
                    foreach (var region in layerRegionList)
                    {
                        if (region.regionID == iRegionID)
                        {
                            // take all shapes for the region out of the map
                            myBingMapLayer.Children.Remove(region.regionShape);


                            // add the new shape back into the map
                            AddMapShapeToRegion(region, mapShape, boundingBox);
                            layerRegionList.Add(region);
                            ShowRegionIfInView(region);
                            break;

                        }
                    }
                }
            }
        }

        private static void AddMapShapeToRegion( Region region, MultiMapShapeBase mapShape,BoundingBoxDTO boundingBox)
        {
            if (mapShape != null)
            {
                // add shape features
                mapShape.Tag = region;
                mapShape.Stroke = region.details.unselected.stroke;
                mapShape.StrokeThickness = region.details.unselected.strokeThickness;
                Region.AddShapeEventHandlers(mapShape);
                region.AddShape(mapShape);
                region.boundingBox = boundingBox;
                AddToolTip(region.regionShape, region.regionID.ToString());
            }
        }


        public static void AddToolTip(MultiMapShapeBase shp, string ttText)
        {
            if (shp != null)
            {
                // Add Tooltip 
                var tooltipObject = new StackPanel();

                var title = new TextBlock();
                title.FontWeight = FontWeights.Bold;
                title.Text = ttText;
                tooltipObject.Children.Add(title);

                ToolTipService.SetToolTip(shp, tooltipObject);
            }
        }

        

        //private void TurnPointIntoIcon()
        //{
        //     turn the point into a polygon
        //    MapMultiPolygon mapPoly = new MapMultiPolygon();
        //    mapPoly.Vertices = new List<LocationCollection>();
        //    // todo: Modify point BuildIcon for custom icons instead of a fixed square
        //    SharpMap.Geometries.Point smPoint = (SharpMap.Geometries.Point)gm;
        //    mapPoly.Vertices.Add(BuildIcon(myBingMap, new Location(smPoint.Y, smPoint.X)));
        //    mapShape = mapPoly;
        //}

        //private static LocationCollection BuildIcon(Map myBingMap, Location loc)
        //{
        //    // Build Point inside box that is approximately (2*side) meters per side
        //    double side = 10;
        //    double mpp = MercatorUtility.ZoomToScale(new Size(512, 512), myBingMap.ZoomLevel, loc);
        //    double offset = side / mpp;

        //    System.Windows.Point pt = myBingMap.LocationToViewportPoint(loc);

        //    pt.X -= offset;
        //    pt.Y -= offset;
        //    Location nw = myBingMap.ViewportPointToLocation(pt);

        //    pt.X += (2 * offset);
        //    pt.Y += (2 * offset);
        //    Location se = myBingMap.ViewportPointToLocation(pt);

        //    LocationRect rect = new LocationRect(nw,se);

        //    LocationCollection polygonLocation = new LocationCollection();
        //    polygonLocation.Add(rect.Northeast);
        //    polygonLocation.Add(rect.Northwest);
        //    polygonLocation.Add(rect.Southwest);
        //    polygonLocation.Add(rect.Southeast);
        //    polygonLocation.Add(rect.Northeast);
        //    return polygonLocation;
        //}
    

        public void LoadRegionsData(int layerID, ObservableCollection<RegionDataDTO> regionsDataList)
        {
            // set up the fill control so it knows about the data max/mins on the layer so it can do a fill if asked
            if (regionsDataList != null)
            {
                // set up the fill control so it knows about the data max/mins on the layer so it can do a fill if asked
                RegionFill.regionFillInstance.AggregateLayerData(layerID, regionsDataList);
            }


            // now load all the new shapes
            foreach (Region currentRegion in layerRegionList)
            {
                if (regionsDataList != null)
                {
                    // find the data for this region and store it in the region data object
                    // need to check for null here because new district won't have data yet
                    List<RegionDataDTO> regionDataList = (from rgn in regionsDataList
                                                          where currentRegion.regionID == rgn.regionID
                                                          select rgn).ToList();
                    currentRegion.ReplaceData(regionDataList);

                }
            }

        }
        public void ClearRegions()
        {
            layerRegionList.Clear();
            myBingMapLayer.Children.Clear();
        }

        public void HideRegions()
        {
            foreach (var region in layerRegionList)
            {

                region.Hide();
            }
        }

        public void ShowRegions()
        {
            if (isVisible)
            {
                foreach (var region in layerRegionList)
                {
                    ShowRegionIfInView(region);
                }
            }
            else
            {
                HideRegions();
            }
        }

        private void ShowRegionIfInView(Region region) 
        {
            //, LocationRect locationRect, BoundingBoxDTO regionBoundingBoxDTO)

            if ((region.boundingBox == null)
                | (region.boundingBox.northWest == null)
                | (region.boundingBox.southEast == null)
            )
            {
                return;
            }

            if ((region.boundingBox.southEast.latitude > myBingMap.BoundingRectangle.North)
                | (region.boundingBox.northWest.latitude < myBingMap.BoundingRectangle.South)
                | (region.boundingBox.northWest.longitude > myBingMap.BoundingRectangle.East)
                | (region.boundingBox.southEast.longitude < myBingMap.BoundingRectangle.West)
                )
            {
                return;
            }
            region.Show();
        }


        private void ShowLayer()
        {
            if (isVisible)
            {
                RefreshShapesAndDataInView();
            }
            else
            {
                HideRegions();
            }
        }

    }

}
